GitHub ActionsとAWSのOIDC連携に使用するリソースをAWS CDKで作る
こんにちは、CX事業本部 IoT事業部の若槻です。
GitHub ActionsではOpenID Connect(OIDC)がサポートされたため、AWSなどと安全にキーのやり取りが可能となっています。
GitHub ActionsをAWSとOIDC連携する場合は、「ID Provider」と「IAM Role」をAWS上に作成する必要があるのですが、これらリソースをAWS CDKで作ってコードで管理するようにしてみました。
やってみた
以前作ったID Providerの確認、削除
以前にコンソールから手動で作成した同じくGitHubとのOIDC連携用のID Providerを取得します。
$ OPEN_ID_CONNECT_PROVIDER_ARN=<OPEN_ID_CONNECT_PROVIDER_ARN> $ aws iam get-open-id-connect-provider \ --open-id-connect-provider-arn ${OPEN_ID_CONNECT_PROVIDER_ARN} { "Url": "token.actions.githubusercontent.com", "ClientIDList": [ "sts.amazonaws.com" ], "ThumbprintList": [ "6938fxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ], "CreateDate": "2022-02-28T14:20:17.398000+00:00", "Tags": [] }
URL
およびClientIDList
をCDKで作るときにも指定して上げれば良さそうです。
ちなみに同じURLのID Providerは複数作成できないので、ここで削除しておきます。
$ aws iam delete-open-id-connect-provider \ --open-id-connect-provider-arn ${OPEN_ID_CONNECT_PROVIDER_ARN}
CDKコード
CDK Stackで次の2つのConstructを作成します。
- ID Provider
- IAM Role(およびInline Policy)
import { Construct } from 'constructs'; import { Stack, StackProps, aws_iam } from 'aws-cdk-lib'; export interface AwsCdkV2ProjectStackProps extends StackProps { principalFederatedSub: string; } export class AwsCdkV2ProjectStack extends Stack { constructor(scope: Construct, id: string, props: AwsCdkV2ProjectStackProps) { super(scope, id, props); const accountId = Stack.of(this).account; const region = Stack.of(this).region; const gitHubIdProvider = new aws_iam.OpenIdConnectProvider( this, 'GitHubIdProvider', { url: 'https://token.actions.githubusercontent.com', clientIds: ['sts.amazonaws.com'], } ); const oidcDeployRole = new aws_iam.Role(this, 'GitHubOidcRole', { roleName: 'github-oidc-role', assumedBy: new aws_iam.FederatedPrincipal( gitHubIdProvider.openIdConnectProviderArn, { StringLike: { 'token.actions.githubusercontent.com:sub': props.principalFederatedSub, }, }, 'sts:AssumeRoleWithWebIdentity' //これを忘れるとStatementのActionが'sts:AssumeRole'となりOIDCでのAssumeRoleで使えなくなる。 ), }); const deployPolicy = new aws_iam.Policy(this, 'deployPolicy', { policyName: 'deployPolicy', statements: [ new aws_iam.PolicyStatement({ effect: aws_iam.Effect.ALLOW, actions: [ 's3:getBucketLocation', 's3:List*', 'cloudformation:CreateStack', 'cloudformation:CreateChangeSet', 'cloudformation:DeleteChangeSet', 'cloudformation:DescribeChangeSet', 'cloudformation:DescribeStacks', 'cloudformation:DescribeStackEvents', 'cloudformation:ExecuteChangeSet', 'cloudformation:GetTemplate', ], resources: [ 'arn:aws:s3:::*', `arn:aws:cloudformation:${region}:${accountId}:stack/CDKToolkit/*`, `arn:aws:cloudformation:${region}:${accountId}:stack/*/*`, ], }), new aws_iam.PolicyStatement({ effect: aws_iam.Effect.ALLOW, actions: ['s3:PutObject', 's3:GetObject'], resources: [`arn:aws:s3:::cdk-*-assets-${accountId}-${region}/*`], }), new aws_iam.PolicyStatement({ effect: aws_iam.Effect.ALLOW, actions: ['ssm:GetParameter'], resources: [ `arn:aws:ssm:${region}:${accountId}:parameter/cdk-bootstrap/*/version`, ], }), new aws_iam.PolicyStatement({ effect: aws_iam.Effect.ALLOW, actions: ['iam:PassRole'], resources: [ `arn:aws:iam::${accountId}:role/cdk-*-cfn-exec-role-${accountId}-${region}`, ], }), ], }); oidcDeployRole.attachInlinePolicy(deployPolicy); } }
CDK AppでprincipalFederatedSub
をConstructに注入します。ここではAccountはcm-rwakatsuki
、Repositoryはaws-cdk-v2-project
とし、どのブランチからも使用可能(*
)としています。必要に応じて書き換えてください。
#!/usr/bin/env node import 'source-map-support/register'; import * as cdk from 'aws-cdk-lib'; import { AwsCdkV2ProjectStack } from '../lib/aws-cdk-v2-project-stack'; const app = new cdk.App(); new AwsCdkV2ProjectStack(app, 'AwsCdkV2ProjectStack', { env: { region: 'ap-northeast-1' }, principalFederatedSub: 'repo:cm-rwakatsuki/aws-cdk-v2-project:ref:refs/heads/*', });
cdk deploy
でCDK Stackをデプロイします。
動作確認
GitHub側でActions SecretとしてIAM RoleのArnを登録します。
次のWorkflowで試してみます。OIDCにより取得したJWTを使用してAssumeRoleを行い、取得した一時クレデンシャルでCDK Deployを行っています。
on: push: paths-ignore: - '**/*.md' jobs: integration: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Cache CDK Dependency uses: actions/cache@v3 id: cache_cdk_dependency_id env: cache-name: cache-cdk-dependency with: path: node_modules key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }} restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}- - name: Install CDK Dependency if: ${{ steps.cache_cdk_dependency_id.outputs.cache-hit != 'true' }} run: npm ci - name: CDK Test run: npm run test deploy: runs-on: ubuntu-latest if: ${{ github.ref_name == 'main' }} needs: integration env: AWS_OIDC_ROLE_ARN: ${{ secrets.AWS_OIDC_ROLE_ARN }} AWS_REGION: ap-northeast-1 permissions: id-token: write contents: read steps: - name: debug run: | echo AWS_OIDC_ROLE_ARN: ${AWS_OIDC_ROLE_ARN/::*:/::XXXXXXXXXXXX:} - name: Checkout uses: actions/checkout@v3 - name: Assume Role uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: ${{ env.AWS_OIDC_ROLE_ARN }} aws-region: ${{env.AWS_REGION}} - name: Cache CDK Dependency uses: actions/cache@v3 id: cache_cdk_dependency_id env: cache-name: cache-cdk-dependency with: path: node_modules key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }} - name: Deploy run: npm run deploy
Workflowを実行すると、OIDCによるAssumeRoleが行われ、CDK Deployを行うことができました!
注意点
ID ProviderのConstructクラスは2種類ある
今回ID Providerの作成に使用したのはOpenIdConnectProvider
というOIDC対応のID Providerを作成するConstructクラスです。
これと似ているものとしてSampProvider
というSAML2.0対応のID Providerを作成するものもあるので、取り違えないように注意。
同じURLのID Providerが既に作成されていないか注意する
デプロイしようとするID Providerと同じURLのものがすでに作成済みの場合は次のようなエラーとなります。よって冒頭で前回作成分の削除を行う必要がありました。
11:19:36 PM | CREATE_FAILED | Custom::AWSCDKOpenIdConnectProvider | IdProvider/Resource/Default Received response status [FAILED] from custom resource. Message returned: EntityAlreadyExists: Provider with url https://token. actions.githubusercontent.com already exists.
参考
- get-open-id-connect-provider — AWS CLI 2.5.8 Command Reference
- delete-open-id-connect-provider — AWS CLI 2.5.8 Command Reference
- class PolicyStatement · AWS CDK
以上